import {renderHook, waitFor} from 'sentry-test/reactTestingLibrary';

import type {
  FeedbackIntegration,
  UseFeedbackOptions,
} from 'sentry/components/feedbackButton/useFeedbackSDKIntegration';
import * as useFeedbackSDKIntegration from 'sentry/components/feedbackButton/useFeedbackSDKIntegration';
import {GlobalFeedbackForm, useFeedbackForm} from 'sentry/utils/useFeedbackForm';

const mockForm = {
  appendToDom: jest.fn(),
  open: jest.fn(),
  close: jest.fn(),
  removeFromDom: jest.fn(),
};

const mockFeedback = {
  createForm: jest.fn().mockResolvedValue(mockForm),
} as unknown as FeedbackIntegration;

const defaultOptions: NonNullable<UseFeedbackOptions> = {
  colorScheme: 'light' as const,
  submitButtonLabel: '',
  messagePlaceholder: '',
  formTitle: '',
};

describe('useFeedbackForm', () => {
  beforeEach(() => {
    jest
      .spyOn(useFeedbackSDKIntegration, 'useFeedbackSDKIntegration')
      .mockReturnValue({feedback: mockFeedback, defaultOptions});
    jest.clearAllMocks();
  });

  it('can open the form using useFeedbackForm', async () => {
    const {result} = renderHook(useFeedbackForm, {wrapper: GlobalFeedbackForm});
    const openForm = result.current;

    expect(openForm).not.toBeNull();

    // Calling openForm() should create a form and append it to the DOM
    await openForm!();

    expect(mockFeedback.createForm).toHaveBeenCalledTimes(1);
    expect(mockForm.appendToDom).toHaveBeenCalledTimes(1);
    expect(mockForm.open).toHaveBeenCalledTimes(1);
  });

  it('reuses the old form instance if same options are provided', async () => {
    const {result} = renderHook(useFeedbackForm, {wrapper: GlobalFeedbackForm});
    const openForm = result.current;

    await openForm!({formTitle: 'foo'});
    expect(mockFeedback.createForm).toHaveBeenLastCalledWith(
      expect.objectContaining({...defaultOptions, formTitle: 'foo'})
    );

    expect(mockForm.removeFromDom).not.toHaveBeenCalled();

    await openForm!({formTitle: 'foo'});
    expect(mockFeedback.createForm).toHaveBeenLastCalledWith(
      expect.objectContaining({...defaultOptions, formTitle: 'foo'})
    );

    // Should not be removed from DOM
    expect(mockForm.removeFromDom).toHaveBeenCalledTimes(0);

    // Should only have been created once
    expect(mockFeedback.createForm).toHaveBeenCalledTimes(1);
    expect(mockForm.appendToDom).toHaveBeenCalledTimes(1);

    // Should have been opened twice
    expect(mockForm.open).toHaveBeenCalledTimes(2);
  });

  it('creates a new form instance if different options are provided', async () => {
    const {result} = renderHook(useFeedbackForm, {wrapper: GlobalFeedbackForm});
    const openForm = result.current;

    await openForm!({formTitle: 'foo'});
    expect(mockFeedback.createForm).toHaveBeenLastCalledWith(
      expect.objectContaining({...defaultOptions, formTitle: 'foo'})
    );

    expect(mockForm.removeFromDom).not.toHaveBeenCalled();

    await openForm!({formTitle: 'bar'});
    expect(mockFeedback.createForm).toHaveBeenLastCalledWith(
      expect.objectContaining({...defaultOptions, formTitle: 'bar'})
    );

    // Shoul have been removed once and added twice
    expect(mockForm.removeFromDom).toHaveBeenCalledTimes(1);
    expect(mockFeedback.createForm).toHaveBeenCalledTimes(2);
    expect(mockForm.appendToDom).toHaveBeenCalledTimes(2);
    expect(mockForm.open).toHaveBeenCalledTimes(2);
  });

  it('cleans up on unmount', async () => {
    const {result, unmount} = renderHook(useFeedbackForm, {wrapper: GlobalFeedbackForm});
    const openForm = result.current;

    await openForm!();
    unmount();

    await waitFor(() => {
      expect(mockForm.removeFromDom).toHaveBeenCalledTimes(1);
    });
  });
});
